package net.gdface.service.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;

import net.gdface.db.DatabaseManager;
import net.gdface.httpclient.AsyncClientManager;
import net.gdface.httpclient.HostManager;
import net.gdface.httpclient.SyncClientManager;
import net.gdface.service.client.LocalApp;
import net.gdface.service.client.FetchImage;
import net.gdface.service.client.SaveImage;
import net.gdface.service.client.SaveImageImpl;
import net.gdface.service.client.WorkDataJSON;
import net.gdface.utils.Judge;
import net.gdface.worker.QueueManager;
import net.gdface.worker.QueueManagerImpl;
import net.gdface.worker.WorkerManager;
import net.gdface.worker.WorkerManagerFactory;

public class KeyImg extends LocalApp {
	private static final Logger logger = LoggerFactory.getLogger(KeyImg.class);
	// /////////////////////配置变量,在loadParametersFromProperties中初始化///////////////////
	private boolean saveDb;
	//由构造方法调用loadParametersFromProperties初始化，不能在这里初始化，否则会覆盖已经输入的值
	private Set<String> keywords;
	//////////////////////////////////////////////////////////////////////////////////////
	private int chkFlag;
	private FetchImage saveProcessor;
	private List<Future<?>> keysFutures = new ArrayList<Future<?>>();
	protected ExecutorService keysThreadPool;
	/**
	 * 下载任务队列管理器(一级)
	 */
	private QueueManager<RemoteImageWOC> queueLevel1;
	/**
	 * 保存任务队列管理器(二级)
	 */
	private QueueManager<WorkDataJSON> queueLevel2;
	private WorkerManager workerPoolLevel1;
	private WorkerManager workerPoolLevel2;
	/**
	 * 同步客户端最大连接数
	 */
	private int syncClientMaxConnections;
	/**
	 * 同步客户端每个主机最大连接数
	 */
	private int syncClientMaxConnectionsPerHost;
	/**
	 * 异步客户端最大连接数
	 */
	private int asyncClientMaxConnections;
	/**
	 * 异步客户端每个主机最大连接数
	 */
	private int asyncClientMaxConnectionsPerHost;
	/**
	 * 异步客户端私有线程池大小
	 */
	private int asyncClientThreadPoolSize;
	/**
	 * 同时工作的对象数目的安全阀值
	 */
	private Double downloadWorkSafeThreshold;
	public KeyImg() {
		super();
		options.addOption(Option.builder(KEY_OPTION).longOpt(KEY_OPTION_LONG)
				.desc(KEY_OPTION_DESC).required().numberOfArgs(Option.UNLIMITED_VALUES).build());
		
		options.addOption(Option.builder(SAVEDB_OPTION).longOpt(SAVEDB_OPTION_LONG)
				.desc(SAVEDB_OPTION_DESC).build());
		
		options.addOption(Option.builder().longOpt(MAXCON_OPTION_LONG)
				.desc(MAXCON_OPTION_DESC).required().numberOfArgs(1).type(Number.class).build());
	}

	@Override
	protected Map<String, Object> getDefaultValueMap() {
		
		defaultValue.setProperty(MAXCON_OPTION_LONG,asyncClientMaxConnections);

		return super.getDefaultValueMap();
	}

	@Override
	public void loadConfig(Options options, CommandLine cmd) throws ParseException {
		super.loadConfig(options, cmd);
		String[] keys = getProperty(KEY_OPTION_LONG);
		keywords.addAll(Lists.transform(Arrays.asList(keys),(a)->a.toLowerCase()));
		if(hasProperty(KEY_OPTION_LONG)){
			saveDb = true;
		}
		asyncClientMaxConnections = getProperty(MAXCON_OPTION_LONG);
	}

	@Override
	protected void loadParametersFromProperties() {
		super.loadParametersFromProperties();
		CONFIG.setPrefix(KeyImg.class.getName() + ".");
		this.saveDb=CONFIG.getPropertyBaseType("saveDb", true);
		this.syncClientMaxConnections = CONFIG.getPropertyInteger("syncClientMaxConnections", -1);
		this.syncClientMaxConnectionsPerHost = CONFIG.getPropertyInteger("syncClientMaxConnectionsPerHost", -1);
		this.asyncClientMaxConnections = CONFIG.getPropertyInteger("asyncClientMaxConnections", -1);
		this.asyncClientMaxConnectionsPerHost = CONFIG.getPropertyInteger("asyncClientMaxConnectionsPerHost", -1);
		this.asyncClientThreadPoolSize = CONFIG.getPropertyInteger("asyncClientThreadPoolSize", -1);
		this.downloadWorkSafeThreshold=CONFIG.getPropertyDouble("downloadWorkSafeThreshold", -1D);
		//搜索关键字，以空格分隔
		String keyStr = CONFIG.getProperty("keywords");		
		if(!Judge.isEmpty(keyStr)){
			if(null==this.keywords)
				this.keywords=new HashSet<String>();
			String[] keys = keyStr.split("\\s+");
			for(String key:keys){
				this.keywords.add(key.toLowerCase());
			}
		}
	}

	
	protected void createDbInstance() throws Exception {
		DatabaseManager.createSingleton(root, reSearch, runDbInMemory);
	}

	@Override
	protected void init() throws Exception {
		super.init();
		chkFlag = SaveImage.CHECK_FACE | SaveImage.CHECK_DUP_LOCAL | (saveDb ? SaveImage.CHECK_DUP_IN_FACEDB : 0);
		keysThreadPool = Executors.newCachedThreadPool();
		SaveImageImpl.setImageSizeLimit(this.imgMinWidth, this.imgMinHeight, this.imgMaxWidth, this.imgMaxHeight);
		SaveImageImpl.setAddImageParameters(this.addImageFaceNum, this.addImageOverwrite);
		SaveImageImpl.setGlobalFinishedSignalObject(this.isFinished);
		this.saveProcessor=new SaveImageImpl(this.root, chkFlag,this.facedb);
		this.queueLevel1 = new QueueManagerImpl<RemoteImageWOC>(1*1000, "DQ", this.workQueueSize);
		this.queueLevel2 = new QueueManagerImpl<WorkDataJSON>(1*1000, "SQ", this.workQueueSize);
		
		DownloadWorker.set(queueLevel1,saveProcessor, 2000, downloadWorkSafeThreshold);
		SaveWorkerKey.set(queueLevel2, saveProcessor, CUSTOM_RETRY_STRATEGY, 2000);
		//RemoteImageWOC负责两个队列的数据交换
		RemoteImageWOC.set(queueLevel1, queueLevel2, saveProcessor);		
		this.workerPool.shutdownNow();		
		this.workerPoolLevel1=WorkerManagerFactory.newFixedThreadPool(Math.max(this.maxWorkerThreads>>5, 4), DownloadWorker.class, queueLevel1, 5000, "L1");
		this.workerPoolLevel2=WorkerManagerFactory.newFixedThreadPool(this.maxWorkerThreads, SaveWorkerKey.class, queueLevel2, 5000, "L2");
		AsyncClientManager.createSingleton(asyncClientMaxConnections, asyncClientMaxConnectionsPerHost, asyncClientThreadPoolSize);
		SyncClientManager.createSingleton(syncClientMaxConnections, syncClientMaxConnectionsPerHost);
		DatabaseManager.getInstance().backuphooks.register(HostManager.getInstance());
	}

	protected String getAppName() {
		return "SEARCH IMAGE";
	}

	protected void startApp() throws InterruptedException {
		this.workerPoolLevel1.startManager();
		this.workerPoolLevel2.startManager();
		if (this.keywords.isEmpty()) {
			logger.info("没有定义搜索关键词");
		} else {
			Iterator<String> it = this.keywords.iterator();
			while (it.hasNext()) {
				String entry = it.next();
				startWord(entry);
			}
			try {
				for (Future<?> f : this.keysFutures) {
					f.get();
				}
			} catch (ExecutionException e) {
				logger.error(e.getCause().getMessage(), e.getCause());
			} finally {
				// 中止并等待所有关键字线程结束
				 this.keysThreadPool.shutdown();
				shutdownAndAwaitTermination(this.keysThreadPool);
				
				this.workerPoolLevel1.stopManager(300, TimeUnit.SECONDS);
				this.workerPoolLevel2.stopManager(300, TimeUnit.SECONDS);				
				try {
					//关闭httpclient，释放资源
					SyncClientManager.getInstance().close();
					AsyncClientManager.getInstance().close();
				} catch (IOException e) {
					logger.error(e.getMessage(),e);
				}
				logger.info("all keyword threads finished.所有关键字线程结束");
			}
		}
	}

	private void startWord(String keyword) {
		final SearchImgImpl so360 = new SearchImgImpl360So(keyword, new SearchSimilarImgImplBaidu(queueLevel1), saveProcessor);
		so360.setGlobalFinishedSignalObject(this.isFinished);
		Future<?> f = keysThreadPool.submit(so360);
		keysFutures.add(f);		
	}

	public static void main(String[] args) throws Exception {
		new KeyImg()
			.parseCommandLine(args)
			.self(KeyImg.class)
			.start();
	}

}
